/*树套数
* 1.树套树
    一棵树套着另一棵树 线段树套平衡树、权值线段树套线段树等

* 本题:
    权值线段树套线段树
    1.离散化
    2.建立权值线段树
        权值线段树以权值为端点，每个节点里面维护的是其下标(与普通线段树相反)
        权值存的是对应的所有下标，每一段权值区间中存储的就是权值区间中的所有权值对应的所有下标
    3.标记持久化
        标记不向下传递 sum add
        add: 给一段下标加上一个数c，对应权值线段树是给一个数值c加上一段下标
            对于每一个节点需要进入到内层线段树进行修改，相当于在内层线段树下标在[l, r]的范围内每个位置都多一个数
            内层线段树维护的是每个下标的个数，相当于在[l, r]中每个位置都+1，这是区间修改操作，使用懒标记add维护
        sum: 
*/

#define DEBUG
// #pragma GCC optimize("O1,O2,O3,Ofast")
// #pragma GCC optimize("no-stack-protector,unroint-loops,fast-math,inline")
// #pragma GCC target("avx,avx2,fma")
// #pragma GCC target("sse,sse2,sse3,sse4,sse4.1,sse4.2,ssse3")

#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;
using ll=long long;
const int N=50010, P=N*17*17, M=N<<2, INF=0x3f3f3f3f; //log(N)=17

inline int read(){
	int x=0;
	bool f=0;
	char c=getchar();
	while(c<'0'||c>'9'){
		if(c=='-') f=1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		x=(x<<3)+(x<<1)+c-48;
		c=getchar();
	}
	return f?-x:x;
}

struct segNode{
    int l, r; //左右区间
    ll sum, add; //持久标记 维护区间内下标总数 对当前节点所代表的区间内所有元素进行的统一修改量
} seg[P]; //平衡树 内层树
int idx;

struct SegNode{
    int l, r; 
    int root; //区间[l,r]对应平衡树的根节点
} SEG[M]; //线段树 外层树

struct Query{
    int op, l, r, c;
}q[N];

int n, m;
vector<int> nums;

inline int get(int c) //离散化之后的结果
{
    return lower_bound(nums.begin(), nums.end(), c) - nums.begin();
}

inline int intersection(int a, int b, int c, int d) //求 [a, b] 和 [c, d] 之间的相交节点数（保证相交）
{
    return min(b, d) - max(a, c) + 1;
}

void in_modify(int u, int l, int r, int L, int R) //内层下标线段树，在下标L~R每个位置+1
{
    seg[u].sum+=intersection(l, r, L, R);
    if(L<=l && r<=R)
    {
        seg[u].add++;
        return;
    }

    int mid=l+r>>1;
    if(L<=mid)
    {
        if(!seg[u].l) seg[u].l=++idx; //左孩子不存在，动态开点
        in_modify(seg[u].l, l, mid, L, R); //更新左半区间
    }
    if(R>mid)
    {
        if(!seg[u].r) seg[u].r=++idx; //右孩子不存在，动态开点
        in_modify(seg[u].r, mid+1, r, L, R);
    }
}   

ll getSum(int u, int l, int r, int L, int R, ll add) //内层下标线段树，查询L~R的节点个数
{
    if(L<=l && r<=R) return seg[u].sum+add*(r-l+1);

    int mid=l+r>>1;
    ll res=0;
    add+=seg[u].add;
    if(L<=mid)
    {
        if(seg[u].l) res+=getSum(seg[u].l, l, mid, L, R, add);
        else res += add*intersection(l, mid, L, R); //左孩子不存在，说明没有更新
    }
    if(R>mid)
    {
        if(seg[u].r) res+=getSum(seg[u].r, mid+1, r, L, R, add);
        else res += add*intersection(mid+1, r, L, R); //左孩子不存在，说明没有更新
    }
    return res;
}

void build(int u, int l, int r) //线段树
{
    SEG[u]={l, r, ++idx}; //外层离散化
    if(l==r) return; //区间叶节点
    int mid=l+r>>1;
    build(u<<1, l, mid);
    build(u<<1|1, mid+1, r);
}

void out_modify(int u, int l, int r, int c)
{
    auto &SU=SEG[u];
    in_modify(SU.root, 1, n, l, r); //1~n全部+1
    if(SU.l==SU.r) return;
    int mid=SU.l+SU.r>>1;

    if(c<=mid) out_modify(u<<1, l, r, c);
    else out_modify(u<<1|1, l, r, c);
}

int query_val_by_rank(int u, int l, int r, ll rank) 
{
    auto &SU=SEG[u];

    if(SU.l==SU.r) return SU.r;
    int mid=SU.l+SU.r>>1;
    ll k=getSum(SEG[u<<1|1].root, 1, n, l, r, 0);

    if(k>=rank) return query_val_by_rank(u<<1|1, l, r, rank);  //查询在右半区间中下标为[l, r]的节点个数
    return query_val_by_rank(u<<1, l, r, rank-k);
}

signed main()
{
    #ifdef DEBUG
        freopen("./in.txt","r",stdin);
        freopen("./out.txt","w",stdout);
    #endif
    
    n=read(); m=read();
    for(int i=0; i<m; i++) 
    {
        q[i].op=read(), q[i].l=read(), q[i].r=read(), q[i].c=read();
        if(q[i].op==1) nums.push_back(q[i].c);
    }
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    build(1, 0, nums.size()-1); //外层权值线段树
    for(int i=0; i<m; i++) 
    { 
        int op=q[i].op, l=q[i].l, r=q[i].r, c=q[i].c;
        if(op == 1) out_modify(1, l, r, get(c)); //将[l, r]中每个位置加入一个c
        else if(op==2) printf("%d\n", nums[query_val_by_rank(1, l, r, c)]); //查询[l, r]中的第c大数

    }
    return 0;
}
